package sm.hibernate.type;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.mina.core.buffer.IoBuffer;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.usertype.CompositeUserType;
import org.hibernate.usertype.ParameterizedType;

import sm.codec.CharacterCodec;
import sm.codec.api.Codec;
import sm.codec.api.CodecFactory;
import sm.codec.exception.EncodeException;
import sm.codec.type.RC;
import sm.codec.type.Struct;
import sm.def.Type;
import sm.util.SMMap;
import sm.util.Util;

import com.google.gson.Gson;

public abstract class AbstractArrayType implements CompositeUserType,
		ParameterizedType {
	private Object fieldTypeDef;

	private Codec codec = CodecFactory.RC_CODEC;

	private static final int MAX_SIZE = 65536;

	@Override
	public void setParameterValues(Properties parameters) {
		String jsonArray = parameters.getProperty("fieldTypeDef");
		if (jsonArray != null) {
			Gson gson = new Gson();
			fieldTypeDef = gson.fromJson(jsonArray, Object.class);
		}
	}

	@Override
	public String[] getPropertyNames() {
		return new String[] {};
	}

	@Override
	public Object getPropertyValue(Object component, int property)
			throws HibernateException {
		return null;
	}

	@Override
	public void setPropertyValue(Object component, int property, Object value)
			throws HibernateException {
	}

	@Override
	public Class returnedClass() {
		return List.class;
	}

	@Override
	public boolean equals(Object x, Object y) throws HibernateException {
		return Util.equals(x, y);
	}

	@Override
	public int hashCode(Object x) throws HibernateException {
		return x == null ? 0 : x.hashCode();
	}

	@Override
	public Object deepCopy(Object value) throws HibernateException {
		if (value instanceof ArrayList) {
			ArrayList<Object> array = (ArrayList<Object>) value;
			return array.clone();
		} else {
			return null;
		}
	}

	@Override
	public boolean isMutable() {
		return true;
	}

	@Override
	public Serializable disassemble(Object value, SessionImplementor session)
			throws HibernateException {
		return (Serializable) deepCopy(value);
	}

	@Override
	public Object assemble(Serializable cached, SessionImplementor session,
			Object owner) throws HibernateException {
		return deepCopy(cached);
	}

	@Override
	public Object replace(Object original, Object target,
			SessionImplementor session, Object owner) throws HibernateException {
		return deepCopy(original);
	}

	protected abstract String getText(ResultSet rs, String[] names,
			SessionImplementor session, Object owner)
			throws HibernateException, SQLException;

	protected abstract void setText(PreparedStatement st, Object value,
			int index, SessionImplementor session) throws HibernateException,
			SQLException;

	@Override
	public Object nullSafeGet(ResultSet rs, String[] names,
			SessionImplementor session, Object owner)
			throws HibernateException, SQLException {
		String text = getText(rs, names, session, owner);

		List<Object> result = null;
		if (text != null) {
			text = (String) RCTypeHelper.dbStringToJava(text);
			result = new ArrayList<Object>();

			if (text.length() > 0) {
				try {
					String[] valueStrs = text.split("\\n");
					if (fieldTypeDef instanceof Number) {
						int type = ((Number) fieldTypeDef).intValue();
						for (String valueStr : valueStrs) {
							Object value = Util.parse(valueStr, type);
							result.add(value);
						}
					} else {
						for (String valueStr : valueStrs) {
							byte[] bytes = valueStr
									.getBytes(CharacterCodec.CHARSET);
							IoBuffer bf = IoBuffer.wrap(bytes);
							RC rc = (RC) codec.decode(bf);
							Object fieldValue = rc;
							List<Object> values = rc.getValues();
							if (values != null && values.size() == 1) {
								Object value = values.get(0);
								if (value instanceof Struct) {
									Struct struct = (Struct) value;
									if (struct.getType() == Type.ARRAY_TYPE) {
										fieldValue = RCTypeHelper
												.arrayToList(struct.getValue(),
														fieldTypeDef);
									} else if (struct.getType() == Type.STRUCTURE_TYPE) {
										fieldValue = RCTypeHelper
												.structureToMap(
														struct.getValue(),
														(Map<String, Object>) fieldTypeDef);
									}

								}
							}
							result.add(fieldValue);
						}
					}
				} catch (Exception e) {
					throw new HibernateException(e.getMessage(), e);
				}
			}
		}

		return result;
	}

	@Override
	public void nullSafeSet(PreparedStatement st, Object value, int index,
			SessionImplementor session) throws HibernateException, SQLException {
		List<Object> array = (List<Object>) value;
		String text = null;
		if (array != null) {
			StringBuilder builder = new StringBuilder();
			int arraySize = array.size();
			for (int i = 0; i < arraySize; i++) {
				Object fieldValue = array.get(i);
				if (fieldValue != null) {
					String valueStr = null;
					if (fieldTypeDef instanceof Number) {
						int type = ((Number) fieldTypeDef).intValue();
						valueStr = Util.toString(fieldValue, type);
					} else {
						Struct struct = null;
						if (value instanceof List) {
							List<Object> fieldArray = (List<Object>) value;
							List<Object> values = RCTypeHelper.listToArray(
									fieldArray, fieldTypeDef);
							struct = new Struct(values, Type.ARRAY_TYPE);
						} else if (value instanceof SMMap) {
							SMMap fieldStructure = (SMMap) value;
							List<Object> values = RCTypeHelper.mapToStructure(
									fieldStructure,
									(Map<String, Object>) fieldTypeDef);
							struct = new Struct(values, Type.STRUCTURE_TYPE);
						}
						RC rc = null;
						if (struct != null) {
							List<Object> values = new ArrayList<Object>();
							values.add(struct);
							rc = new RC(values);
						} else {
							rc = (RC) value;
						}
						IoBuffer buffer = IoBuffer.allocate(MAX_SIZE);
						buffer.setAutoExpand(true);
						try {
							codec.encode(buffer, rc);
						} catch (EncodeException e) {
							throw new HibernateException(e.getMessage(), e);
						}
						buffer.flip();
						byte[] bytes = new byte[buffer.limit()];
						buffer.get(bytes);
						try {
							valueStr = new String(bytes, CharacterCodec.CHARSET);
						} catch (UnsupportedEncodingException e) {
							throw new HibernateException(e.getMessage(), e);
						}
					}

					builder.append(valueStr);
					if (i < arraySize - 1) {
						builder.append("\n");
					}
				}
			}
			text = (String) RCTypeHelper.javaStringToDB(builder.toString());
		}

		setText(st, text, index, session);
	}
}
